package tester

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"gitee.com/AlexWoo/arpc"
	"gitee.com/AlexWoo/arpc/alog"
	"gitee.com/AlexWoo/arpc/anet"
	"gitee.com/AlexWoo/arpc/ws"
)

var (
	clctx = &alog.LogCtx{
		Prefix: func() string { return "Client" },
	}
	timeFormat = "2006-01-02T15:04:05.000Z0700"
)

type Client struct {
	bn *anet.Net

	wg *sync.WaitGroup

	sc     *script
	tslock sync.Mutex
	ts     map[string]*tester
	tr     *TestRecorder

	addr string

	total  uint32
	finish uint32
}

func CreateTesterClient(wg *sync.WaitGroup, addr string, path string, script string, con uint32, n uint32) *Client {
	c := &Client{
		wg:   wg,
		ts:   map[string]*tester{},
		tr:   &TestRecorder{},
		addr: addr,
	}

	// init conf
	conf := &anet.Conf{}
	arpc.FillDefault(conf)
	conf.Path = path

	wsconf := &ws.ClientConf{}
	arpc.FillDefault(wsconf)
	wsconf.Path = path

	// init anet
	var anetwg sync.WaitGroup
	c.bn = anet.CreateNet(conf, ws.Connect, c, &anetwg)

	// init script
	sc, err := parseFile(script, slctx)
	if err != nil {
		alog.LogError(clctx, "init script error: %v", err)
		return nil
	}
	c.sc = sc

	c.batch(con, n)

	return c
}

func (c *Client) batch(concurrency uint32, total uint32) {
	ba := concurrency / 10
	bas := 0
	bac := 0

	if concurrency%10 != 0 {
		ba++
	}

	for {
		now := time.Now()
		for j := 0; j < int(ba) && bas < int(concurrency); j++ {
			c.wg.Add(1)
			newTester(c, c.tr, c.sc)

			total--
			bas++

			if total == 0 {
				return
			}
		}

		bac++
		time.Sleep(100*time.Millisecond - time.Since(now) - 1*time.Millisecond)

		if bac%10 == 0 {
			s, f, finish, totoal := c.Report()
			fmt.Printf("... %s UAC Tester Result --- successd: %d failed: %d total: %d hangup: %d\n",
				time.Now().Format(timeFormat), s, f, totoal, totoal-finish)
			bas = 0
		}
	}
}

func (c *Client) Report() (uint32, uint32, uint32, uint32) {
	return c.tr.success, c.tr.failed, c.finish, c.total
}

func (c *Client) Dispatch(msg *anet.Msg) {
	name := msg.AppId + "/" + msg.ChannelId + "/" + msg.UserId

	te := c.ts[name]
	if te == nil {
		alog.LogError(clctx, "Receive [%s] but script not exist", msg.Str())
		return
	}

	alog.LogInfo(te.lctx, "Receive msg [%s]", msg.Str())

	if msg.Type == anet.MsgType_Closed {
		c.tslock.Lock()
		delete(c.ts, name)
		c.tslock.Unlock()
		atomic.AddUint32(&c.finish, 1)
		c.wg.Done()
		return
	}

	te.recvMsg(msg)
}

func (c *Client) Send(msg *anet.Msg, te *tester) {
	if msg.Type == anet.MsgType_Join && msg.Code == 0 {
		name := msg.AppId + "/" + msg.ChannelId + "/" + msg.UserId
		c.tslock.Lock()
		if c.ts[name] == nil {
			atomic.AddUint32(&c.total, 1)
			c.ts[name] = te
		}
		c.tslock.Unlock()
	}

	alog.LogInfo(te.lctx, "Send msg [%s]", msg.Str())
	c.bn.Send(msg, c.addr)
}
